/*
 * The MIT License
 *
 * Copyright 2012 Anonymous.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package pentomino.ui;

import java.awt.Container;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.swing.*;
import pentomino.Program;
import pentomino.logic.PentominoGame;
import pentomino.logic.Playground;

/**
 *
 * Represents the application main frame.
 */
public class PentominoFrame extends JFrame {
    
    List<FigurePanel> toolBarFigurePanels;
    PlaygroundPanel playgroundPanel;
    StatusBar statusBar;
    
    PentominoGame game;
    
    /**
     * Initializes a new instance of the PentominoFrame class by default.
     */
    public PentominoFrame()
    {
        super("Pentomino Puzzle");
        
        game = new PentominoGame(Playground.FIELD6X10);
        toolBarFigurePanels = new ArrayList<FigurePanel>();
        playgroundPanel = new PlaygroundPanel(game);
        statusBar = new StatusBar(game);
        
        setupPlaygroundPanel();
        
        // Frame setup:
        setLocationByPlatform(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        setSize(640, 480);

        // Frame components setup:
        
        Container pane = getContentPane();
        
        pane.setLayout(new GridBagLayout());
        
        GridBagConstraints c = new GridBagConstraints();
        
        JPanel toolBar = createToolBar(); // The toolbar with game figures.
               
        c.anchor = GridBagConstraints.WEST; 
        c.fill   = GridBagConstraints.BOTH;  
        c.gridheight = 1;
        c.gridwidth  = 2; 
        c.weightx = 0.2;
        c.weighty = 0.9;
        pane.add(toolBar, c);
        
        c.fill = GridBagConstraints.BOTH;
        c.gridwidth = 0;
        c.weightx = 0.8;
        c.weighty = 0;
        c.gridx = GridBagConstraints.RELATIVE;
        pane.add(playgroundPanel, c);
        
        c.fill = GridBagConstraints.BOTH;
        c.gridwidth = 0;
        c.gridheight = 2;
        c.weightx = 1;
        c.weighty = 0.1;
        pane.add(statusBar, c);
                
        // Initializes a bars:
        initializeMenu();
        resetActions();
        
        // Creates and attaches a frame events:
        registerEvents();
        
        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                if (game.hasFinished())
                    saveScoreEvent();
                
                Program.saveSettings();
                super.windowClosing(e);
            }
            
        });
    }
    
    /**
     * Creates a Tool bar.
     * @return a swing component JPanel
     */
    private JPanel createToolBar()
    {
        JPanel toolbar = new JPanel();
        
        GridLayout layout = new GridLayout(PentominoGame.FIGURES.length / 2, 2);

        toolbar.setLayout(layout);
        toolbar.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, InterfaceHelper.BORDER_COLOR));
        
        for (int i = 0; i < PentominoGame.FIGURES.length; i++)
        {
            FigurePanel fpanel = new FigurePanel(PentominoGame.FIGURES[i]);
            
            fpanel.setToolTipText(
                    String.format("Shortcut: %c", 
                                  PentominoGame.FIGURES[i].getType(),
                                  PentominoGame.FIGURES[i].getType()
                    )
            );
            
            // Mouse listener for each tool:        
            fpanel.addMouseListener(new MouseAdapter() {
                
                @Override
                public void mouseReleased(MouseEvent e) {
                    super.mouseReleased(e);

                    FigurePanel panel = (FigurePanel)e.getSource();
                    
                    if (!panel.isEnabled())
                        return;
                        
                    game.setCurrentFigure(panel.getFigure());
                    
                    for (int i = 0; i < toolBarFigurePanels.size(); i++)
                        toolBarFigurePanels.get(i).setPressed(false);
                    
                    panel.setPressed(true);
                    resetActions();
                }
                
            });
            
            toolBarFigurePanels.add(fpanel);
            toolbar.add(fpanel);
        }
        
        return toolbar;
    }
    
    /**
     * A last game scores saving event handler.
     */
    private void saveScoreEvent()
    {
        long score = game.getScore();
        
        String s = (String)JOptionPane.showInputDialog(
                this,
                String.format("You have gained a %d scores.\nPlease, enter you name:",
                              score),
                "Store the result",
                JOptionPane.PLAIN_MESSAGE,
                null,
                null, 
                null);
        
        if (s != null)
        {
            Program.addNewHighscore(s, score);
        }
    }
    
    /**
     * A new game event hander.
     */
    private void newGameEvent()
    {
        Object[] options = { "Yes", "No" };
        
        int n = JOptionPane.showOptionDialog(this, 
                "Do you want to start a new game?", 
                "Start a new game", 
                JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.WARNING_MESSAGE, 
                null, options, options[1]);
        
        if (n == 0) {
            if (game.hasFinished())
                saveScoreEvent();
            
            Object[] possibilities = { "3x20", "4x15", "5x12", "6x10" };
            
            String s = (String)JOptionPane.showInputDialog(this,
                    "Choice a game field size:",
                    "Start a new game",
                    JOptionPane.PLAIN_MESSAGE,
                    null,
                    possibilities,
                    possibilities[0]);
            
            if ((s != null) && (s.length() > 0))
            {
                int fieldType = 0;
                
                if (s == possibilities[0])
                    fieldType = Playground.FIELD3X20;
                else if (s == possibilities[1])
                    fieldType = Playground.FIELD4X15;
                else if (s == possibilities[2])
                    fieldType = Playground.FIELD5X12;
                else if (s == possibilities[3])
                    fieldType = Playground.FIELD6X10;
                
                game.startNew(fieldType);
                playgroundPanel.update(game);
                statusBar.stopWatch();
                
                for (int i = 0; i < toolBarFigurePanels.size(); i++)
                    toolBarFigurePanels.get(i).setEnabled(true);
                
                resetActions();
                repaint();                
            }
        }
    }
    
    /**
     * Setups the game field controller.
     */
    private void setupPlaygroundPanel()
    {
        /* Adds the events handlers. */
        
        playgroundPanel.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                super.mouseMoved(e);
                playgroundPanel.doMoveAction(e.getX(), e.getY());
            }
                        
        });
        
        playgroundPanel.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseReleased(MouseEvent e) {
                super.mouseReleased(e);
                boolean success;                
                
                if (game.hasFinished())
                    return;
                
                switch (e.getButton())
                {
                    case MouseEvent.BUTTON1: // Left click:
                        boolean startFlag = game.hasStarted();
                        success = playgroundPanel.doInsertAction(e.getX(), e.getY());
                        
                        if (success) // Inserts the figure if it possible.
                        {
                            int n = game.getLastInsertedId();
                            
                            if (n >= 0)
                            {
                                toolBarFigurePanels.get(n).setEnabled(false);
                            }
                            
                            if (startFlag)
                                statusBar.startWatch();
                            
                            game.countAction();
                            
                            if (game.hasFinished())
                                statusBar.stopWatch();
                        }
                        else // Tries to take the figure from the game field.
                        {
                            success = playgroundPanel.doRemoveAction(e.getX(), e.getY());
                            
                            if (success)
                            {
                                int n = game.getLastRemovedId();
                                
                                if (n >= 0)
                                {
                                    toolBarFigurePanels.get(n).setEnabled(true);
                                    game.setCurrentFigure(toolBarFigurePanels.get(n).getFigure());
                                }
                            }
                        }
                        
                        break;
                    case MouseEvent.BUTTON3: // Right click:
                        success = playgroundPanel.doRemoveAction(e.getX(), e.getY());
                        
                        if (success) // Removes the figure from the game field.
                        {
                            int n = game.getLastRemovedId();
                            
                            if (n >= 0)
                            {
                                toolBarFigurePanels.get(n).setEnabled(true);
                            }
                            
                            game.countAction();
                        }
                        
                        break;
                }
                resetActions();
                repaint();
            }
            
        });
    }
    
    /**
     * Initializes a frame menu strip.
     */
    private void initializeMenu()
    {
        JMenuBar menu = new JMenuBar();
        
        this.setJMenuBar(menu);
        
        // A game menu:
        JMenu mgame = new JMenu("Game");
        JMenuItem mstart = new JMenuItem(
                "New", 
                new ImageIcon(this.getClass().getResource("resources/newgame.png"))
        );
        
        JMenuItem mhs = new JMenuItem(
                "Highscores",
                new ImageIcon(this.getClass().getResource("resources/hs24.png"))
        );
        
        JMenuItem mexit = new JMenuItem(
                "Exit", 
                new ImageIcon(this.getClass().getResource("resources/exit.png"))
        );
                
        mstart.setAccelerator(KeyStroke.getKeyStroke("control N"));
        mstart.setMnemonic('N');
        mstart.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                newGameEvent();
            }
        });
        
        mhs.setMnemonic('s');
        mhs.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                StringBuilder sb = new StringBuilder();
                
                HashMap<String, Long> hs = Program.getHighscores();

                if (hs != null)
                {
                    sb.append("\n");
                    
                    for (String key : hs.keySet())
                    {
                        sb.append(key).append(": ");
                        sb.append(hs.get(key)).append('\n');
                    }
                    
                    if (!hs.isEmpty())
                    {
                        JOptionPane.showMessageDialog(
                                null, 
                                sb.toString(), 
                                "Highscores", 
                                JOptionPane.INFORMATION_MESSAGE);
                    }
                    else
                    {
                        JOptionPane.showMessageDialog(
                                null,
                                "Highscores not found...",
                                "Highscores",
                                JOptionPane.WARNING_MESSAGE);
                    }
                }
            }
        });
        
        mexit.setAccelerator(KeyStroke.getKeyStroke("control Q"));
        mexit.setMnemonic('x');
        mexit.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
            
        });
        
        mgame.add(mstart);
        mgame.add(mhs);
        mgame.addSeparator();
        mgame.add(mexit);
        mgame.setMnemonic('G');
        
        menu.add(mgame);
        
        // An Actions menu:
        JMenu mactions = new JMenu("Actions");
        JMenuItem mundo = new JMenuItem(
                "Undo",
                new ImageIcon(this.getClass().getResource("resources/undo.png"))
        );
        JMenuItem mrotl = new JMenuItem(
                "Rotate Left",
                new ImageIcon(this.getClass().getResource("resources/rotatecc24.png"))
        );
        JMenuItem mrotr = new JMenuItem(
                "Rotate Right",
                new ImageIcon(this.getClass().getResource("resources/rotatec24.png"))
        );
        JMenuItem mfliph = new JMenuItem(
                "Flip Horizontal",
                new ImageIcon(this.getClass().getResource("resources/fliph24.png"))
        );
        JMenuItem mflipv = new JMenuItem(
                "Flip Vertical",
                new ImageIcon(this.getClass().getResource("resources/flipv24.png"))
        );
        
        mundo.setAccelerator(KeyStroke.getKeyStroke("control Z"));
        mundo.setMnemonic('U');
        mundo.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                int n = game.getLastInsertedId();
                                
                if (n >= 0)
                {
                    toolBarFigurePanels.get(n).setEnabled(true);
                    game.undo();
                    resetActions();
                    game.countAction();
                    repaint();
                }
            }
        });
        
        mrotl.setAccelerator(KeyStroke.getKeyStroke("control G"));
        mrotl.setMnemonic('L');
        mrotl.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (game.getCurrentFigure() != null)
                {
                    game.getCurrentFigure().rotateLeft();
                    game.countAction();
                    repaint();
                }
            }
        });
        
        mrotr.setAccelerator(KeyStroke.getKeyStroke("control C"));
        mrotr.setMnemonic('R');
        mrotr.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                
                if (game.getCurrentFigure() != null)
                {
                    game.getCurrentFigure().rotateRight();
                    game.countAction();
                    repaint();
                }
            }
        });
        
        mfliph.setAccelerator(KeyStroke.getKeyStroke("control H"));
        mfliph.setMnemonic('H');
        mfliph.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (game.getCurrentFigure() != null)
                {
                    game.getCurrentFigure().flipHorizontal();
                    game.countAction();
                    repaint();
                }
            }
        });
        
        mflipv.setAccelerator(KeyStroke.getKeyStroke("control V"));
        mflipv.setMnemonic('V');
        mflipv.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (game.getCurrentFigure() != null)
                {
                    game.getCurrentFigure().flipVertical();
                    game.countAction();
                    repaint();
                }
            }
        });
        
        mactions.add(mundo);
        mactions.addSeparator();
        mactions.add(mrotl);
        mactions.add(mrotr);
        mactions.addSeparator();
        mactions.add(mfliph);
        mactions.add(mflipv);
        mactions.setMnemonic('A');
        
        menu.add(mactions);
        
        // A help menu:
        JMenu mhelp = new JMenu("Help");
        JMenuItem mabout = new JMenuItem(
                "About",
                new ImageIcon(this.getClass().getResource("resources/about24.png"))
        );
        JMenuItem mhtp = new JMenuItem(
                "How to play...",
                new ImageIcon(this.getClass().getResource("resources/howto24.png"))
        );
        
        mabout.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                StringBuilder sb = new StringBuilder();
                
                sb.append("Pentomino Puzzle Game version 1.0\n");
                sb.append("Copyright (c) 2012 Alexey Grigoriev (aggpys@gmail.com)\n\n");
                sb.append("This software is licensed by the MIT license. For more information please,\n");
                sb.append("visit http://opensource.org/licenses/mit-license.html\n\n");
                sb.append("http://code.google.com/p/web-pentomino/\n");
                
                JOptionPane.showMessageDialog(
                        null, 
                        sb.toString(),
                        "About",
                        JOptionPane.INFORMATION_MESSAGE);
            }
        });
        
        mhtp.setAccelerator(KeyStroke.getKeyStroke("F1"));
        mhtp.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Frame[] frames = getFrames();
                boolean searchResult = false;
                
                for (int i = 0; i < frames.length; i++)
                    if (frames[i].getTitle().equals("Pentomino Help"))
                    {
                        frames[i].setVisible(true);
                        frames[i].setFocusableWindowState(true);
                        
                        searchResult = true;
                    }
                
                if (!searchResult)
                {
                    JFrame help = new HelpFrame();

                    help.setSize(640, 480);
                    help.setVisible(true);
                }
            }
        });
        
        mhelp.add(mhtp);
        mhelp.add(mabout);
        mhelp.setMnemonic('H');
        
        menu.add(mhelp);
    }
    
    /**
     * Enables/disables actions.
     */
    private void resetActions()
    {
        JMenuItem undo = getJMenuBar().getMenu(1).getItem(0);
        JMenuItem rotl = getJMenuBar().getMenu(1).getItem(2);
        JMenuItem rotr = getJMenuBar().getMenu(1).getItem(3);
        JMenuItem fliph = getJMenuBar().getMenu(1).getItem(5);
        JMenuItem flipv = getJMenuBar().getMenu(1).getItem(6);
        
        undo.setEnabled(game.hasExecutedActions() && !game.hasFinished());
        
        boolean hasSelected = game.getCurrentFigure() != null;
        
        rotl.setEnabled(hasSelected);
        rotr.setEnabled(hasSelected);
        fliph.setEnabled(hasSelected);
        flipv.setEnabled(hasSelected);
    }
    
    /**
     * Creates and attaches a frame events.
     */
    private void registerEvents()
    {
        // Keyboard events:
        addKeyListener(new KeyAdapter() {
            
            @Override
            public void keyReleased(KeyEvent e) {
                super.keyReleased(e);
                                  
                int code = e.getKeyCode();
                char c;
                
                switch (code)
                {
                    // Selecting the figures:
                    case KeyEvent.VK_F:
                    case KeyEvent.VK_I:
                    case KeyEvent.VK_L:
                    case KeyEvent.VK_N:
                    case KeyEvent.VK_P:
                    case KeyEvent.VK_T:
                    case KeyEvent.VK_U:
                    case KeyEvent.VK_V:
                    case KeyEvent.VK_W:
                    case KeyEvent.VK_X:
                    case KeyEvent.VK_Y:
                    case KeyEvent.VK_Z:
                        c = Character.toUpperCase(e.getKeyChar());
                        
                        for (int i = 0; i < toolBarFigurePanels.size(); i++)
                        {
                            FigurePanel panel = toolBarFigurePanels.get(i);
                            panel.setPressed(false);

                            if (panel.getFigure().is(c) &
                                panel.isEnabled())
                            {
                                game.setCurrentFigure(c);
                                panel.setPressed(true);
                                playgroundPanel.setToolVisible(true);
                            }
                                    
                        }
                        resetActions();
                        repaint();
                        break;
                        
                    // Move figures by the keyboard:
                    case KeyEvent.VK_LEFT:
                    case KeyEvent.VK_RIGHT:
                    case KeyEvent.VK_UP:
                    case KeyEvent.VK_DOWN:
                        playgroundPanel.setToolVisible(true);
                        playgroundPanel.moveTarget(code);
                        repaint();
                        break;
                    // Insert a figure:
                    case KeyEvent.VK_ENTER:
                        boolean startFlag = game.hasStarted();
                        boolean success = playgroundPanel.doInsertAction();               

                        if (success)
                        {
                            int n = game.getLastInsertedId();
                            
                            if (n >= 0)
                            {
                                toolBarFigurePanels.get(n).setEnabled(false);
                            }
                            
                            if (startFlag)
                                statusBar.startWatch();
                            
                            game.countAction();
                        }
                        resetActions();
                        repaint();
                        break;
                    // Cancel action:
                    case KeyEvent.VK_ESCAPE:
                        if (game.getCurrentFigure() != null)
                        {
                            c = game.getCurrentFigure().getType();
                            
                            for (int i = 0; i < toolBarFigurePanels.size(); i++)
                                if (toolBarFigurePanels.get(i).getFigure().is(c))
                                    toolBarFigurePanels.get(i).setEnabled(true);
                            
                            game.setCurrentFigure(null);
                        }
                        resetActions();
                        repaint();
                        break;
                }
            }
            
        });
    }
}
